package suites.distributor {
	import org.flexunit.asserts.assertEquals;
	import org.flexunit.asserts.assertTrue;
	import org.flexunit.asserts.fail;
	import org.splink.library.distributor.Dtype;
	import org.splink.library.distributor.Devent;
	import org.splink.library.distributor.Distributor;
	import org.splink.library.distributor.IDevent;
	import org.splink.library.distributor.IDistributor;
	import org.splink.library.utils.ArrayUtils;

	/**
	 * @author Max Kugland
	 */
	public class DistributorTest implements IDistributor {
		private const TYPE_1 : Dtype = new Dtype("type1");		private const TYPE_2 : Dtype = new Dtype("type2");		private const TYPE_3 : Dtype = new Dtype("type3");		private const TYPE_4 : Dtype = new Dtype("type4");		private var _distributor : Distributor;
		private var _completed : Array;

		[Before]
		public function before() : void {
			_completed = new Array();
			_distributor = new Distributor();
		}

		[After]
		public function after() : void {
			finalize();
			_distributor = null;
			_completed = null;
		}

		[Test(description="register and distribute with no args")]
		public function testNoArgs() : void {
			register(TYPE_1, onCompleteNoArgs);
			distribute(new Devent(this, TYPE_1));
			assertEquals(1, _completed.length);
		}

		[Test(description="register and distribute with one arg")]
		public function testArgs() : void {
			register(TYPE_1, onCompleteWithArgs, "args");
			distribute(new Devent(this, TYPE_1));
			assertEquals(1, _completed.length);
		}

		[Test(description="register and distribute an event of type1 (with args) and and event of type2 (without args)")]
		public function testTwoTypes() : void {
			register(TYPE_1, onCompleteWithArgs, "args");
			register(TYPE_2, onCompleteNoArgs);
			distribute(new Devent(this, TYPE_1));
			distribute(new Devent(this, TYPE_2));
			assertEquals(2, _completed.length);
		}

		[Test(description="register, unregister and distribute")]
		public function testUnregister() : void {
			register(TYPE_1, onCompleteNoArgs);
			unregister(TYPE_1, onCompleteNoArgs);
			distribute(new Devent(this, TYPE_1));
			assertEquals(0, _completed.length);
		}
		
		[Test]
		public function testUnregisterTwoFunctionsSameType() : void {
			var exec : int = 0;
			var fnc : Function = function(e : Devent):void {
				fail();
			};
			
			register(TYPE_1, fnc);			register(TYPE_1, function(e : Devent):void {
				exec++;
			});
			register(TYPE_1, function(e : Devent):void {
				exec++;
			});
			unregister(TYPE_1, fnc);
			
			distribute(new Devent(this, TYPE_1));
			assertEquals(2, exec);
		}
		
		[Test]
		public function testUnregisterDuringDispatch() : void {
			var flag : Boolean = false;
			var fnc : Function = function(e : Devent):void {
				unregister(TYPE_1, fnc);
			};
			var fnc2 : Function = function(e : Devent):void {
				flag = true;
			};
			
			register(TYPE_1, fnc);			register(TYPE_1, fnc2);
			distribute(new Devent(this, TYPE_1));
			assertTrue(flag);
		}

		[Test(description="register, finalize and distribute")]
		public function testFinalize() : void {
			register(TYPE_1, onCompleteNoArgs);
			register(TYPE_2, onCompleteWithArgs, "args");			register(TYPE_3, onCompleteWithArgs, "args");			register(TYPE_4, onCompleteWithArgs);
			finalize();
			distribute(new Devent(this, TYPE_1));			distribute(new Devent(this, TYPE_2));
			assertEquals(0, _completed.length);
		}

		[Test(description="register same type and fnc twice, expect only one event")]
		public function testOnce() : void {
			var count : int = 0;
			var fnc : Function = function(e : Devent):void {
				if(count > 0) fail();
				count++;
			};
			register(TYPE_1, fnc);
			register(TYPE_1, fnc);
			distribute(new Devent(this, TYPE_1));
		}

		[Test]
		public function testPriority() : void {
			var expected : Array = [1,2,3,4];			var result : Array = [];
			register(TYPE_1, function(e : Devent):void {
				result.push(4);
				assertTrue(ArrayUtils.eq(expected, result));
			}, null, 10);			register(TYPE_1, function(e : Devent):void {
				result.push(3);
			}, null, 20);
			register(TYPE_1, function(e : Devent):void {
				result.push(1);
			}, null, 50);
			register(TYPE_1, function(e : Devent):void {
				result.push(2);
			}, null, 40);
			
			distribute(new Devent(this, TYPE_1));
		}
		
		[Test]
		public function testZeroPriority() : void {
			var expected : Array = [1,2,3,4];
			var result : Array = [];
			register(TYPE_1, function(e : Devent):void {
				result.push(1);
			});
			register(TYPE_1, function(e : Devent):void {
				result.push(2);
			});
			register(TYPE_1, function(e : Devent):void {
				result.push(3);
			});
			register(TYPE_1, function(e : Devent):void {
				result.push(4);
				assertTrue(ArrayUtils.eq(expected, result));
			});
			
			distribute(new Devent(this, TYPE_1));
		}

		private function onCompleteWithArgs(event : Devent, args : Object = null) : void {
			assertTrue(event.source == this);			assertEquals(args, "args");
			_completed.push("");
		}

		private function onCompleteNoArgs(event : Devent, args : Object = null) : void {
			assertTrue(event.source == this);
			assertEquals(args, null);
			_completed.push("");
		}

		public function register(type : Dtype, fnc : Function, params : Object = null, priority : uint = uint.MIN_VALUE) : IDistributor {
			_distributor.register(type, fnc, params, priority);
			return this;
		}

		public function unregister(type : Dtype, fnc : Function) : IDistributor {
			_distributor.unregister(type, fnc);
			return this;
		}

		public function distribute(event : IDevent) : void {
			_distributor.distribute(event);
		}

		public function finalize() : void {
			_distributor.finalize();
		}
	}
}
